package com.echo.im.domain.group.service;


import cn.dev33.satoken.temp.SaTempUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.echo.im.client.IMClient;
import com.echo.im.common.contant.Command;
import com.echo.im.common.enums.MessageStatus;
import com.echo.im.common.enums.MessageType;
import com.echo.im.common.exception.GlobalException;
import com.echo.im.client.model.IMUserInfo;
import com.echo.im.client.model.IMMessage;
import com.echo.im.common.result.ResultEnum;
import com.echo.im.common.util.CommaTextUtils;
import com.echo.im.common.vo.GroupMessageVO;
import com.echo.im.common.vo.PrivateMessageVO;
import com.echo.im.domain.friend.manager.FriendManager;
import com.echo.im.domain.group.dto.GroupDTO;
import com.echo.im.domain.group.dto.HandlerGroupInviteDTO;
import com.echo.im.domain.group.manager.GroupManager;
import com.echo.im.domain.group.manager.GroupMemberManager;
import com.echo.im.domain.group.vo.GroupInviteDTO;
import com.echo.im.domain.group.vo.GroupInviteVO;
import com.echo.im.domain.group.vo.GroupMemberVO;
import com.echo.im.domain.group.vo.GroupVO;
import com.echo.im.domain.message.manager.GroupMessageManager;
import com.echo.im.domain.user.manager.UserManager;
import com.echo.im.domain.user.vo.UserVO;
import com.echo.im.infrastructure.session.SessionContext;
import com.echo.im.infrastructure.session.UserSession;
import com.echo.im.infrastructure.temp.GroupInviteTemp;
import com.echo.im.repository.dao.entity.Group;
import com.echo.im.repository.dao.entity.GroupMember;
import com.echo.im.repository.dao.entity.GroupMessage;
import com.echo.im.repository.dao.entity.User;
import com.echo.im.repository.dao.mapper.GroupMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 群组服务实现类
 * @author echo
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements GroupService {

    private final GroupMemberManager groupMemberManager;
    private final GroupManager groupManager;
    private final FriendManager friendManager;
    private final UserManager userManager;
    private final IMClient imClient;
    private final GroupMessageManager groupMessageManager;

    /**
     * 创建群组
     * @param dto 群组信息
     * @return 创建后的群组信息
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public GroupDTO createGroup(GroupDTO dto) {

        groupManager.createGroup(dto);

        // 返回
        log.info("创建群聊，群聊id:{},群聊名称:{}", dto.getId(), dto.getName());
        return dto;
    }

    /**
     * 修改群组信息
     * @param dto 待修改的群组信息
     * @return 修改后的群组信息
     */
    @CacheEvict(key = "#vo.getId()")
    @Transactional(rollbackFor = Exception.class)
    @Override
    public GroupDTO modifyGroup(GroupDTO dto) {
        UserSession session = SessionContext.getSession();

        // 校验是不是群主，只有群主能改信息
        Group group = groupManager.getAndCheckByGroupId(dto.getId());

        //查抄成员信息
        GroupMember member = groupMemberManager.findAndCheckMemberByGroupAndUserId(dto.getId(), session.getUserId());

        // 更新成员信息
        member.setRemarkNickName(dto.getRemarkNickName());
        member.setRemarkGroupName(dto.getRemarkGroupName());
        groupMemberManager.updateById(member);

        // 群主有权修改群基本信息
        if (group.getOwnerId().equals(session.getUserId())) {
            group = BeanUtil.copyProperties(dto, Group.class);
            this.updateById(group);
        }
        dto.setShowNickName(StrUtil.isEmpty(dto.getRemarkNickName()) ? member.getUserNickName() : dto.getRemarkNickName());
        dto.setShowGroupName(StrUtil.isEmpty(dto.getRemarkGroupName()) ? group.getName() : dto.getRemarkGroupName());
        log.info("修改群聊，群聊id:{},群聊名称:{}", group.getId(), group.getName());
        return dto;
    }

    /**
     * 删除群组
     * 1. 删除群组记录
     * 2. 删除群组成员记录
     * 3。推送群聊解散消息
     * @param groupId 群组ID
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteGroup(Long groupId) {
        UserSession session = SessionContext.getSession();
        //校验权限
        Group group = groupManager.checkGroupOwner(session.getUserId(),groupId);

        // 群聊用户id
        List<Long> userIds = groupMemberManager.findUserIdsByGroupId(groupId);


        groupManager.deleteGroup(groupId);


        //推送解散群聊提示
        this.sendTipMessage(groupId, userIds, String.format("'%s'解散了群聊", session.getNickName()));
        log.info("删除群聊，群聊id:{},群聊名称:{}", group.getId(), group.getName());

    }



    /**
     * 根据ID查找群组
     * @param groupId 群组ID
     * @return 群组信息
     */
    @Override
    public GroupVO findById(Long groupId) {
        UserSession session = SessionContext.getSession();

        Group group = groupManager.getAndCheckByGroupId(groupId);

        GroupMember member = groupMemberManager.findAndCheckMemberByGroupAndUserId(groupId, session.getUserId());


        GroupVO vo = BeanUtil.copyProperties(group, GroupVO.class);
        vo.setRemarkGroupName(member.getRemarkGroupName());
        vo.setRemarkNickName(member.getRemarkNickName());
        vo.setShowNickName(member.getShowNickName());
        vo.setShowGroupName(StrUtil.isEmpty(vo.getRemarkGroupName()) ? group.getName() : vo.getRemarkGroupName());
        vo.setQuit(member.getQuit());
        return vo;
    }

    /**
     * 查找所有群组
     * @return 所有群组信息列表
     */
    @Override
    public List<GroupVO> findGroups() {
        UserSession session = SessionContext.getSession();
        // 查询当前用户的群id列表
        List<GroupMember> groupMembers = groupMemberManager.findByUserId(session.getUserId());

        // 一个月内退的群可能存在退群前的离线消息,一并返回作为前端缓存
        groupMembers.addAll(groupMemberManager.findQuitInMonth(session.getUserId()));

        if (groupMembers.isEmpty()) {
            return new LinkedList<>();
        }
        // 拉取群列表
        List<Long> ids = groupMembers.stream().map((GroupMember::getGroupId)).collect(Collectors.toList());
        LambdaQueryWrapper<Group> groupWrapper = Wrappers.lambdaQuery();
        groupWrapper.in(Group::getId, ids);
        List<Group> groups = this.list(groupWrapper);
        // 转vo
        return groups.stream().map(g -> {
            GroupVO vo = BeanUtil.copyProperties(g, GroupVO.class);
            GroupMember member = groupMembers.stream().filter(m -> g.getId().equals(m.getGroupId())).findFirst().get();
            vo.setShowNickName(
                    StrUtil.isEmpty(vo.getRemarkNickName()) ? session.getNickName() : vo.getRemarkNickName());
            vo.setShowGroupName(StrUtil.isEmpty(vo.getRemarkGroupName()) ? g.getName() : vo.getRemarkGroupName());
            vo.setQuit(member.getQuit());
            return vo;
        }).collect(Collectors.toList());
    }

    /**
     * 邀请用户加入群组
     * @param dto 邀请信息
     */
    @Override
    public Map<Long,String> invite(GroupInviteDTO dto) {
        UserSession session = SessionContext.getSession();

        //验证群组信息属实
        groupManager.getAndCheckByGroupId(dto.getGroupId());

        //验证群成员信息
        groupMemberManager.findAndCheckMemberByGroupAndUserId(dto.getGroupId(), session.getUserId());

        //校验群成员数量
        groupMemberManager.checkMemberCount(dto.getFriendIds().size(),dto.getGroupId());

        // 批量检查邀请列表是否为自己的好友
        friendManager.checkFriends(session.getUserId(),dto.getFriendIds());


        //构造邀请人信息
        User user = userManager.getById(session.getUserId());
        UserVO userVO = UserVO.builder().sex(user.getSex())
                .headImageThumb(user.getHeadImageThumb())
                .nickName(user.getNickName())
                .signature(user.getSignature())
                .build();

        //构造群聊信息
        Group group = baseMapper.selectById(dto.getGroupId());
        GroupVO groupVO = new GroupVO();
        groupVO.setHeadImageThumb(group.getHeadImageThumb());
        groupVO.setName(group.getName());

        //合并
        GroupInviteVO inviteVO = new GroupInviteVO();
        inviteVO.setGroup(groupVO);
        inviteVO.setInviter(userVO);

        //返回前端每个用户的临时令牌，由前端进行发送私聊消息进行邀请进入群聊，因为此消息务必要收到ack
        // 所以只能前端发

        //构造tempToken
        //构造临时token
        GroupInviteTemp groupInviteTemp = new GroupInviteTemp();
        groupInviteTemp.setOwner(session.getUserId());
        groupInviteTemp.setUser(0L);
        groupInviteTemp.setGroupId(dto.getGroupId());

        HashMap<Long, String> map = new HashMap<>();

        //分别为每一个人构临时令牌
        for (Long friendId : dto.getFriendIds()) {
            groupInviteTemp.setUser(friendId);
            String tempToken = SaTempUtil.createToken("groupInvite",groupInviteTemp, 200);
            map.put(friendId,tempToken);
        }

        return map;

    }

    /**
     * 处理群聊邀请
     * 1. 去redis查看申请是否过期
     * 2. 如果未过期，则将用户加入群聊
     * 3. 给对方回执
     * @param dto
     */
    @Override
    public void handlerInvite(HandlerGroupInviteDTO dto) {
        UserSession session = SessionContext.getSession();
        //验证临时token，如果无效则抛出异常
        //解析临时token
        GroupInviteTemp groupInviteTemp = SaTempUtil.parseToken("inviteGroup",dto.getTempToken(), GroupInviteTemp.class);

        if (Objects.isNull(groupInviteTemp)){
            throw new GlobalException(ResultEnum.FRIEND_APPLY_EXPIRE);
        }

        //验证token
        if(!groupInviteTemp.getUser().equals(session.getUserId())||
                !groupInviteTemp.getOwner().equals(dto.getTargetUserId())||
                !groupInviteTemp.getGroupId().equals(dto.getTargetGroupId())){
            throw new GlobalException(ResultEnum.NO_PERMISSION);
        }

        //如果拒接，则直接返回
        if (!dto.getIsAccept()){
            return;
        }


        //执行用户加入群聊逻辑，并发送打招呼信息
        // 保存成员数据
        GroupMember groupMember = new GroupMember();
        groupMember.setGroupId(dto.getTargetGroupId());
        groupMember.setUserId(session.getUserId());
        groupMember.setUserNickName(session.getNickName());
        groupMember.setCreatedTime(new Date());
        groupMember.setQuit(false);
        groupMemberManager.save(groupMember);

        // 推送进入群聊消息
        String content="xxx 邀请 xxx 加入群聊";
        this.sendTipMessage(dto.getTargetGroupId(), null, content);
    }

    /**
     * 查找群组成员
     * @param groupId 群组ID
     * @return 群组成员列表
     */
    @Override
    public List<GroupMemberVO> findGroupMembers(Long groupId) {
        Group group = groupManager.getAndCheckByGroupId(groupId);
        List<GroupMember> members = groupMemberManager.findByGroupId(groupId);
        List<Long> userIds = members.stream().map(GroupMember::getUserId).collect(Collectors.toList());
        List<Long> onlineUserIds = imClient.getOnlineUser(userIds);
        return members.stream().map(m -> {
            GroupMemberVO vo = BeanUtil.copyProperties(m, GroupMemberVO.class);
            vo.setShowNickName(m.getShowNickName());
            vo.setShowGroupName(StrUtil.isEmpty(m.getRemarkGroupName()) ? group.getName() : m.getRemarkGroupName());
            vo.setOnline(onlineUserIds.contains(m.getUserId()));
            return vo;
        }).sorted((m1, m2) -> m2.getOnline().compareTo(m1.getOnline())).collect(Collectors.toList());

    }

    /**
     * 退出群组
     * @param groupId 群组ID
     */
    @Override
    public void quitGroup(Long groupId) {
        Long userId = SessionContext.getSession().getUserId();
        Group group = this.getById(groupId);
        if (group.getOwnerId().equals(userId)) {
            throw new GlobalException(ResultEnum.OWNER_CANT_QUIT);
        }
        // 删除群聊成员
        groupMemberManager.removeByGroupAndUserId(groupId, userId);

        //推送退出群聊提示
        this.sendTipMessage(groupId, Collections.singletonList(userId), "您已退出群聊");
        log.info("退出群聊，群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId);
    }

    /**
     * 踢出群组成员
     * @param groupId 群组ID
     */
    @Override
    public void kickGroup(Long groupId, Long delUserId) {
        UserSession session = SessionContext.getSession();

        groupManager.checkGroupOwner(session.getUserId(),groupId);

        if (delUserId.equals(session.getUserId())) {
            throw new GlobalException(ResultEnum.NO_PERMISSION);
        }

        // 删除群聊成员
        groupMemberManager.removeByGroupAndUserId(groupId, delUserId);

        // 推送踢出群聊提示
        this.sendTipMessage(groupId, Arrays.asList(delUserId), "您已被移出群聊");
        log.info("踢出群聊，群聊id:{},用户id:{}", groupId, groupId);

    }



    private void sendTipMessage(Long groupId, List<Long> recvIds, String content) {
        UserSession session = SessionContext.getSession();
        // 消息入库
        GroupMessage message = new GroupMessage();
        message.setContent(content);
        message.setType(MessageType.TIP_TEXT.code());
        message.setStatus(MessageStatus.NORMAL.code());
        message.setSendTime(new Date());
        message.setSendNickName(session.getNickName());
        message.setGroupId(groupId);
        message.setSendId(session.getUserId());
        message.setRecvIds(CommaTextUtils.asText(recvIds));

        // 推送
        GroupMessageVO groupMessageVO = BeanUtil.copyProperties(message, GroupMessageVO.class);
        IMMessage<GroupMessageVO> imGroupMessage =
                new IMMessage<>(groupMessageVO.getId(),groupMessageVO.getSeqId(), Command.PRIVATE_OUT_MESSAGE, true);

        imGroupMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));

        if (CollUtil.isEmpty(recvIds)) {
            // 为空表示向全体发送
            List<Long> userIds = groupMemberManager.findUserIdsByGroupId(groupId);
            imGroupMessage.setRecvIds(userIds);
        } else {
            imGroupMessage.setRecvIds(recvIds);
        }

        imGroupMessage.setData(groupMessageVO);
        imGroupMessage.setSendResult(false);
        imGroupMessage.setSendToSelf(false);

        imClient.sendMessage(imGroupMessage);
    }
}
